CDK Bootstrapをカスタマイズしてみた
こんにちは。たかやまです。
CDKを利用する場合cdk bootstrap
を実施して、アカウントにCDKが利用するECR/IAM/S3/SSMといったリソースを作成する必要があります。
このBootstarpを作成する場合に、組織の制約によってはBootstrapの作成ができなかったり、bootstrapの内容を変えたい場合があります。
このようなニーズを満たすため、Bootstrapをカスタマイズしてみたいと思います。
カスタマイズをするユースケース例
- CloudFormation Hooksを利用してガバナンス強化をしている場合
- IAM Permissions BoundaryでRole作成に制約をつけている場合
- 組織ルールに合わせた機能を有効化したい場合
etc...
やってみた
Bootstrapテンプレートをエクスポートする
cdk bootstrap --show-template
を使うことで、標準でデプロイされるBootstrapのテンプレートを確認することができます。
テンプレート(v2.49.0)
Description: This stack includes resources needed to deploy AWS CDK apps into this environment Parameters: TrustedAccounts: Description: List of AWS accounts that are trusted to publish assets and deploy stacks to this environment Default: "" Type: CommaDelimitedList TrustedAccountsForLookup: Description: List of AWS accounts that are trusted to look up values in this environment Default: "" Type: CommaDelimitedList CloudFormationExecutionPolicies: Description: List of the ManagedPolicy ARN(s) to attach to the CloudFormation deployment role Default: "" Type: CommaDelimitedList FileAssetsBucketName: Description: The name of the S3 bucket used for file assets Default: "" Type: String FileAssetsBucketKmsKeyId: Description: Empty to create a new key (default), 'AWS_MANAGED_KEY' to use a managed S3 key, or the ID/ARN of an existing key. Default: "" Type: String ContainerAssetsRepositoryName: Description: A user-provided custom name to use for the container assets ECR repository Default: "" Type: String Qualifier: Description: An identifier to distinguish multiple bootstrap stacks in the same environment Default: hnb659fds Type: String AllowedPattern: "[A-Za-z0-9_-]{1,10}" ConstraintDescription: Qualifier must be an alphanumeric identifier of at most 10 characters PublicAccessBlockConfiguration: Description: Whether or not to enable S3 Staging Bucket Public Access Block Configuration Default: "true" Type: String AllowedValues: - "true" - "false" Conditions: HasTrustedAccounts: Fn::Not: - Fn::Equals: - "" - Fn::Join: - "" - Ref: TrustedAccounts HasTrustedAccountsForLookup: Fn::Not: - Fn::Equals: - "" - Fn::Join: - "" - Ref: TrustedAccountsForLookup HasCloudFormationExecutionPolicies: Fn::Not: - Fn::Equals: - "" - Fn::Join: - "" - Ref: CloudFormationExecutionPolicies HasCustomFileAssetsBucketName: Fn::Not: - Fn::Equals: - "" - Ref: FileAssetsBucketName CreateNewKey: Fn::Equals: - "" - Ref: FileAssetsBucketKmsKeyId UseAwsManagedKey: Fn::Equals: - AWS_MANAGED_KEY - Ref: FileAssetsBucketKmsKeyId HasCustomContainerAssetsRepositoryName: Fn::Not: - Fn::Equals: - "" - Ref: ContainerAssetsRepositoryName UsePublicAccessBlockConfiguration: Fn::Equals: - "true" - Ref: PublicAccessBlockConfiguration Resources: FileAssetsBucketEncryptionKey: Type: AWS::KMS::Key Properties: KeyPolicy: Statement: - Action: - kms:Create* - kms:Describe* - kms:Enable* - kms:List* - kms:Put* - kms:Update* - kms:Revoke* - kms:Disable* - kms:Get* - kms:Delete* - kms:ScheduleKeyDeletion - kms:CancelKeyDeletion - kms:GenerateDataKey Effect: Allow Principal: AWS: Ref: AWS::AccountId Resource: "*" - Action: - kms:Decrypt - kms:DescribeKey - kms:Encrypt - kms:ReEncrypt* - kms:GenerateDataKey* Effect: Allow Principal: AWS: "*" Resource: "*" Condition: StringEquals: kms:CallerAccount: Ref: AWS::AccountId kms:ViaService: - Fn::Sub: s3.${AWS::Region}.amazonaws.com - Action: - kms:Decrypt - kms:DescribeKey - kms:Encrypt - kms:ReEncrypt* - kms:GenerateDataKey* Effect: Allow Principal: AWS: Fn::Sub: ${FilePublishingRole.Arn} Resource: "*" Condition: CreateNewKey FileAssetsBucketEncryptionKeyAlias: Condition: CreateNewKey Type: AWS::KMS::Alias Properties: AliasName: Fn::Sub: alias/cdk-${Qualifier}-assets-key TargetKeyId: Ref: FileAssetsBucketEncryptionKey StagingBucket: Type: AWS::S3::Bucket Properties: BucketName: Fn::If: - HasCustomFileAssetsBucketName - Fn::Sub: ${FileAssetsBucketName} - Fn::Sub: cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region} AccessControl: Private BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: aws:kms KMSMasterKeyID: Fn::If: - CreateNewKey - Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn} - Fn::If: - UseAwsManagedKey - Ref: AWS::NoValue - Fn::Sub: ${FileAssetsBucketKmsKeyId} PublicAccessBlockConfiguration: Fn::If: - UsePublicAccessBlockConfiguration - BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true - Ref: AWS::NoValue VersioningConfiguration: Status: Enabled UpdateReplacePolicy: Retain DeletionPolicy: Retain StagingBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: Ref: StagingBucket PolicyDocument: Id: AccessControl Version: "2012-10-17" Statement: - Sid: AllowSSLRequestsOnly Action: s3:* Effect: Deny Resource: - Fn::Sub: ${StagingBucket.Arn} - Fn::Sub: ${StagingBucket.Arn}/* Condition: Bool: aws:SecureTransport: "false" Principal: "*" ContainerAssetsRepository: Type: AWS::ECR::Repository Properties: ImageTagMutability: IMMUTABLE RepositoryName: Fn::If: - HasCustomContainerAssetsRepositoryName - Fn::Sub: ${ContainerAssetsRepositoryName} - Fn::Sub: cdk-${Qualifier}-container-assets-${AWS::AccountId}-${AWS::Region} RepositoryPolicyText: Version: "2012-10-17" Statement: - Sid: LambdaECRImageRetrievalPolicy Effect: Allow Principal: Service: lambda.amazonaws.com Action: - ecr:BatchGetImage - ecr:GetDownloadUrlForLayer Condition: StringLike: aws:sourceArn: Fn::Sub: arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:* FilePublishingRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: AWS: Ref: AWS::AccountId - Fn::If: - HasTrustedAccounts - Action: sts:AssumeRole Effect: Allow Principal: AWS: Ref: TrustedAccounts - Ref: AWS::NoValue RoleName: Fn::Sub: cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region} Tags: - Key: aws-cdk:bootstrap-role Value: file-publishing ImagePublishingRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: AWS: Ref: AWS::AccountId - Fn::If: - HasTrustedAccounts - Action: sts:AssumeRole Effect: Allow Principal: AWS: Ref: TrustedAccounts - Ref: AWS::NoValue RoleName: Fn::Sub: cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region} Tags: - Key: aws-cdk:bootstrap-role Value: image-publishing LookupRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: AWS: Ref: AWS::AccountId - Fn::If: - HasTrustedAccountsForLookup - Action: sts:AssumeRole Effect: Allow Principal: AWS: Ref: TrustedAccountsForLookup - Ref: AWS::NoValue - Fn::If: - HasTrustedAccounts - Action: sts:AssumeRole Effect: Allow Principal: AWS: Ref: TrustedAccounts - Ref: AWS::NoValue RoleName: Fn::Sub: cdk-${Qualifier}-lookup-role-${AWS::AccountId}-${AWS::Region} ManagedPolicyArns: - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess Policies: - PolicyDocument: Statement: - Sid: DontReadSecrets Effect: Deny Action: - kms:Decrypt Resource: "*" Version: "2012-10-17" PolicyName: LookupRolePolicy Tags: - Key: aws-cdk:bootstrap-role Value: lookup FilePublishingRoleDefaultPolicy: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - s3:GetObject* - s3:GetBucket* - s3:GetEncryptionConfiguration - s3:List* - s3:DeleteObject* - s3:PutObject* - s3:Abort* Resource: - Fn::Sub: ${StagingBucket.Arn} - Fn::Sub: ${StagingBucket.Arn}/* Effect: Allow - Action: - kms:Decrypt - kms:DescribeKey - kms:Encrypt - kms:ReEncrypt* - kms:GenerateDataKey* Effect: Allow Resource: Fn::If: - CreateNewKey - Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn} - Fn::Sub: arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${FileAssetsBucketKmsKeyId} Version: "2012-10-17" Roles: - Ref: FilePublishingRole PolicyName: Fn::Sub: cdk-${Qualifier}-file-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} ImagePublishingRoleDefaultPolicy: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - ecr:PutImage - ecr:InitiateLayerUpload - ecr:UploadLayerPart - ecr:CompleteLayerUpload - ecr:BatchCheckLayerAvailability - ecr:DescribeRepositories - ecr:DescribeImages - ecr:BatchGetImage - ecr:GetDownloadUrlForLayer Resource: Fn::Sub: ${ContainerAssetsRepository.Arn} Effect: Allow - Action: - ecr:GetAuthorizationToken Resource: "*" Effect: Allow Version: "2012-10-17" Roles: - Ref: ImagePublishingRole PolicyName: Fn::Sub: cdk-${Qualifier}-image-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} DeploymentActionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: AWS: Ref: AWS::AccountId - Fn::If: - HasTrustedAccounts - Action: sts:AssumeRole Effect: Allow Principal: AWS: Ref: TrustedAccounts - Ref: AWS::NoValue Policies: - PolicyDocument: Statement: - Sid: CloudFormationPermissions Effect: Allow Action: - cloudformation:CreateChangeSet - cloudformation:DeleteChangeSet - cloudformation:DescribeChangeSet - cloudformation:DescribeStacks - cloudformation:ExecuteChangeSet - cloudformation:CreateStack - cloudformation:UpdateStack Resource: "*" - Sid: PipelineCrossAccountArtifactsBucket Effect: Allow Action: - s3:GetObject* - s3:GetBucket* - s3:List* - s3:Abort* - s3:DeleteObject* - s3:PutObject* Resource: "*" Condition: StringNotEquals: s3:ResourceAccount: Ref: AWS::AccountId - Sid: PipelineCrossAccountArtifactsKey Effect: Allow Action: - kms:Decrypt - kms:DescribeKey - kms:Encrypt - kms:ReEncrypt* - kms:GenerateDataKey* Resource: "*" Condition: StringEquals: kms:ViaService: Fn::Sub: s3.${AWS::Region}.amazonaws.com - Action: iam:PassRole Resource: Fn::Sub: ${CloudFormationExecutionRole.Arn} Effect: Allow - Sid: CliPermissions Action: - cloudformation:DescribeStackEvents - cloudformation:GetTemplate - cloudformation:DeleteStack - cloudformation:UpdateTerminationProtection - sts:GetCallerIdentity - cloudformation:GetTemplateSummary Resource: "*" Effect: Allow - Sid: CliStagingBucket Effect: Allow Action: - s3:GetObject* - s3:GetBucket* - s3:List* Resource: - Fn::Sub: ${StagingBucket.Arn} - Fn::Sub: ${StagingBucket.Arn}/* - Sid: ReadVersion Effect: Allow Action: - ssm:GetParameter Resource: - Fn::Sub: arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter${CdkBootstrapVersion} Version: "2012-10-17" PolicyName: default RoleName: Fn::Sub: cdk-${Qualifier}-deploy-role-${AWS::AccountId}-${AWS::Region} Tags: - Key: aws-cdk:bootstrap-role Value: deploy CloudFormationExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: cloudformation.amazonaws.com Version: "2012-10-17" ManagedPolicyArns: Fn::If: - HasCloudFormationExecutionPolicies - Ref: CloudFormationExecutionPolicies - Fn::If: - HasTrustedAccounts - Ref: AWS::NoValue - - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess RoleName: Fn::Sub: cdk-${Qualifier}-cfn-exec-role-${AWS::AccountId}-${AWS::Region} CdkBootstrapVersion: Type: AWS::SSM::Parameter Properties: Type: String Name: Fn::Sub: /cdk-bootstrap/${Qualifier}/version Value: "14" Outputs: BucketName: Description: The name of the S3 bucket owned by the CDK toolkit stack Value: Fn::Sub: ${StagingBucket} BucketDomainName: Description: The domain name of the S3 bucket owned by the CDK toolkit stack Value: Fn::Sub: ${StagingBucket.RegionalDomainName} FileAssetKeyArn: Description: The ARN of the KMS key used to encrypt the asset bucket (deprecated) Value: Fn::If: - CreateNewKey - Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn} - Fn::Sub: ${FileAssetsBucketKmsKeyId} Export: Name: Fn::Sub: CdkBootstrap-${Qualifier}-FileAssetKeyArn ImageRepositoryName: Description: The name of the ECR repository which hosts docker image assets Value: Fn::Sub: ${ContainerAssetsRepository} BootstrapVersion: Description: The version of the bootstrap resources that are currently mastered in this stack Value: Fn::GetAtt: - CdkBootstrapVersion - Value
テンプレートを編集する
さきほど出力したテンプレートを書き換えることで編集できます。
今回はSecurityHubに準拠するように、S3ライフサイクル、ECR Repositoryスキャン/ライフサイクル機能を有効にします。
@@ -163,6 +163,14 @@ - UseAwsManagedKey - Ref: AWS::NoValue - Fn::Sub: ${FileAssetsBucketKmsKeyId} + LifecycleConfiguration: + Rules: + - Id: Delete-After-31days + Status: Enabled + ExpirationInDays: 31 + NoncurrentVersionExpiration: + NewerNoncurrentVersions: 3 + NoncurrentDays: 1 PublicAccessBlockConfiguration: Fn::If: - UsePublicAccessBlockConfiguration @@ -197,7 +205,27 @@ ContainerAssetsRepository: Type: AWS::ECR::Repository Properties: + ImageScanningConfiguration: + ScanOnPush: true ImageTagMutability: IMMUTABLE + LifecyclePolicy: + LifecyclePolicyText: | + { + "rules": [ + { + "rulePriority": 1, + "description": "Delete more than 20 images", + "selection": { + "tagStatus": "any", + "countType": "imageCountMoreThan", + "countNumber": 20 + }, + "action": { + "type": "expire" + } + } + ] + } RepositoryName: Fn::If: - HasCustomContainerAssetsRepositoryName
テンプレートを実行する
cdk bootstrap --template
で編集したテンプレートを実行することで、bootstrapをカスタマイズすることができます。指定するテンプレートをここではcustomize-bootstrap.ymlとしています。
> npx cdk bootstrap --template customize-bootstrap.yml Using bootstrapping template from customize-bootstrap.yml ⏳ Bootstrapping environment aws://xxxxxxxxxxxxxx/ap-northeast-1... Trusted accounts for deployment: (none) Trusted accounts for lookup: (none) Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize. CDKToolkit: creating CloudFormation changeset... ✅ Environment aws://xxxxxxxxxxxxxx/ap-northeast-1 bootstrapped.
ちなみにBootstarpの場合cdkでの差分比較は対応していないため、事前に変更差分を確認したい場合にはオプション--no-execute
でCloudFormationの実行は行わずChangeSetを残してCloudFormation上で確認してください。
> npx cdk bootstrap --template customize-bootstrap.yml --no-execute Using bootstrapping template from customize-bootstrap.yml ⏳ Bootstrapping environment aws://xxxxxxxxxxxxxx/ap-northeast-1... Trusted accounts for deployment: (none) Trusted accounts for lookup: (none) Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize. CDKToolkit: creating CloudFormation changeset... Changeset arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxxxx:changeSet/cdk-deploy-change-set/d295e686-54c6-4abc-a916-xxxxxxxxxxxxx created and waiting in review for manual execution (--no-execute) ✅ Environment aws://xxxxxxxxxxxxxx/ap-northeast-1 bootstrapped.
最後に
今回直接テンプレートを編集しBootstrapを更新する方法をご紹介しましたが、「バケット名の変更」や「CMKの利用」など直接オプション指定できるものもあるので、ユースケースに合わせて活用できるオプションがないか確認いただければと思います。
オプション | 内容 |
---|---|
-a, --app | アプリを実行する際に必要なもの:アプリを実行するコマンドライン、またはクラウドアセンブリディレクトリ(例:"node bin/my-app.js")。cdk.jsonまたは~/.cdk.jsonで指定することも可能[string] |
--build | pre-synthのコマンドライン [string] |
-c, --context | コンテキスト文字列を追加(KEY=VALUE) [array] |
-p, --plugin | CDKの機能を拡張するノードパッケージの名前またはパス。複数回指定可能 [array] |
--trace | スタック警告のトレースを印刷する [boolean] |
--strict | 警告のあるスタックを構築しない [boolean] |
--lookups | コンテキストルックアップを実行する(これが無効でコンテキストルックアップを実行する必要がある場合、合成は失敗する) [boolean] [default: true] |
--ignore-errors | 合成エラーを無視する(無効な出力を生成する可能性が高い) [boolean] [default: false] |
-j, --json | テンプレートがSTDOUTに出力されるとき、YAMLの代わりにJSON出力を使用する [boolean] [default: false] |
-v, --verbose | デバッグログを表示する(複数回指定すると冗長性が増す) [count] [default: false] |
--debug | トークンの作成スタックトレースなどの追加デバッグ情報の発信を有効にする [boolean] [default: false] |
--profile | デフォルト環境として指定されたAWSプロファイルを使用する [string] |
--proxy | 指定されたプロキシを使用する。指定しない場合は HTTPS_PROXY 環境変数から読み込む [string] |
--ca-bundle-path | HTTPS リクエストの検証時に使用する CA 証明書へのパス。指定しない場合はAWS_CA_BUNDLE環境変数から読み込む [string] |
-i, --ec2creds | EC2インスタンスの認証情報を取得することを強制する。デフォルト:EC2インスタンスの状態を推測 [boolean] |
--version-reporting | 合成したテンプレートに "AWS::CDK::Metadata" リソースを含める(デフォルトで有効) [boolean] |
--path-metadata | "aws:cdk:path"をインクルードする。CloudFormationのメタデータを各リソースに含める(デフォルトで有効) [boolean] [default: true] |
--asset-metadata | "aws:asset:*"をインクルードする。アセットを利用するリソースのCloudFormationメタデータ(デフォルトで有効)[boolean] [default: true] |
-r, --role-arn | CloudFormationの起動時に使用するRoleのARN [string] |
--staging | アセットを出力ディレクトリにコピーします (無効にするには --no-staging を使用する。SAM CLI でソースファイルをローカルにデバッグする際に必要) [boolean] [default: true] |
-o, --output | 合成したクラウドアセンブリをディレクトリに出力する(デフォルト: cdk.out) [string] |
--notices | 関連する通知の表示 [boolean] |
--no-color | コンソール出力から色などのスタイルを削除する [boolean] [default: false] |
--ci | CIを強制的に検出する。CI=trueの場合、ログはstderrではなくstdoutに送られる [boolean] [default: false] |
--version | バージョン番号の表示 [boolean] |
-b, --bootstrap-bucket-name, | CDK toolkitのバケット名 |
--toolkit-bucket-name | バケットは作成されるため、存在してはならない [string] |
--bootstrap-kms-key-id | SSE-KMSの暗号化に使用するAWS KMSマスターキーID [string] |
--bootstrap-customer-key | ブートストラップバケットのカスタマーマスターキー(CMK)を作成する(課金されますが、パーミッションをカスタマイズできる、モダンブートストラップのみ)[boolean] |
--qualifier | 各ブートストラップスタックで一意でなければならない文字列。デフォルトから変更する場合は、CDKアプリで設定する必要がある。[string] |
--public-access-block-configuration | CDK toolkit バケットでパブリックアクセスをブロックする設定(デフォルトで有効) [boolean] |
-t, --tags | スタックに追加するタグ (KEY=VALUE) [array] [default: []] |
--execute | ChangeSetを実行するかどうか(--no-executeはChangeSetを実行しない) [boolean] [default: true] |
--trust | この環境へのデプロイを実行するために信頼されるべきAWSアカウントID(繰り返すことができる、モダンブートストラップのみ) [array] [default: []] |
--trust-for-lookup | この環境で値を検索するために信頼されるべき AWS アカウント ID (繰り返すことができる、モダンブートストラップのみ) [array] [default: []] |
--cloudformation-execution-policies | この環境へのデプロイメントを実行するロールにアタッチされるべきマネージドポリシーARN(繰り返すことができる、モダンブートストラップのみ) [array] [default: []]. |
-f, --force | テンプレートバージョンをダウングレードする場合でも常にブートストラップする [boolean] [default: false] |
--termination-protection | ブートストラップスタックのCloudFormationの終了保護をトグルする [boolean] |
--show-template | 実際のブートストラップの代わりに、現在のCLIのブートストラップ・テンプレートをstdoutに出力し、カスタマイズできるようにします。 [boolean] [default: false] |
--toolkit-stack-name | 作成する CDK ツールキットスタックの名前 [string] |
--template | 組み込みのテンプレートではなく、与えられたファイルからのテンプレートを使用します (例を得るには --show-template を使用します) [string] |
-h, --help | ヘルプを表示する [boolean] |
以上、たかやま(@nyan_kotaroo)でした。